//This file is a part of StoragePlug for Minecraft.
//http://code.google.com/p/minecraft-storageplug/
//StoragePlug is Copyright (C) 2011 Christopher Smith
//Released under the MIT license:
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights 
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//DEALINGS IN THE SOFTWARE.
//
//Related to Minecraft Copyright (C) 2009-2011 Mojang http://www.minecraft.net/

package net.minecraft.storageplug;

import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;

public class ReadCache
	implements StorageProvider
{
    private final ConcurrentHashMap<Key, DataTree> cache;
    private final StorageProvider next;
    
    public ReadCache(StorageProvider next, boolean eager)
        throws Exception
    {
        this.next = next;
        
        if(eager)
        {
            ArrayList<Key> keys = new ArrayList<Key>(256);

            keys.addAll(next.iterKeys(Key.WorldInfo.class));
            keys.addAll(next.iterKeys(Key.Player.class));
            keys.addAll(next.iterKeys(Key.ItemData.class));
            keys.addAll(next.iterKeys(Key.Chunk.class));
            
            System.out.print("StoragePlug ReadCache: Loading " + keys.size() + " objects to pre-cache");
            
            this.cache = new ConcurrentHashMap<Key, DataTree>(keys.size(), 0.75f, 4);
            
            //load 256 at a time
            for(int i = 0; i < keys.size(); i+=256)
            {
                for(Map.Entry<Key, DataTree> e : next.readDataTreeBatch(keys.subList(i, Math.min(i+256, keys.size()))))
                    cache.put(e.getKey(), e.getValue());
                System.out.print('.');
            }
            
            System.out.println("done");
        }
        else
            this.cache = new ConcurrentHashMap<Key, DataTree>(256, 0.75f, 4);
    }
    
    @Override
    public void writeDataTree(DataTree dt, Key key)
        throws Exception
    {
        cache.put(key, dt);
        next.writeDataTree(dt, key);
    }

    @Override
    public DataTree readDataTree(Key key)
        throws Exception
    {
        try
        {
            if(cache.containsKey(key))
                return cache.get(key);

            DataTree dt = next.readDataTree(key);
            cache.put(key, dt);        

            return dt;
        }
        catch(NullPointerException e)
        {
            return null;
        }
    }

    @Override
    public Collection<? extends Entry<Key, DataTree>> readDataTreeBatch(Collection<? extends Key> list)
        throws Exception
    {
        HashSet<Key> remainingKeys = new HashSet<Key>(list);
        ArrayList<Map.Entry<Key, DataTree>> out = new ArrayList<Map.Entry<Key, DataTree>>(list.size());
        
        for(Key key : list)
            if(cache.containsKey(key))
            {
                remainingKeys.remove(key);
                out.add(new SimpleEntry<Key, DataTree>(key, cache.get(key)));
            }
        
        if(!remainingKeys.isEmpty())
        {
            Collection<? extends Entry<Key, DataTree>> remainingOutput = next.readDataTreeBatch(remainingKeys);
            
            for(Entry<Key, DataTree> e : remainingOutput)
                cache.put(e.getKey(), e.getValue());
            
            out.addAll(remainingOutput);  
        }
        
        return out;
    }

    @Override
    public void writeDataTreeBatch(Collection<? extends Entry<Key, DataTree>> list)
        throws Exception
    {
        for(Entry<Key, DataTree> e : list)
            cache.put(e.getKey(), e.getValue());
        
        next.writeDataTreeBatch(list);
    }

    @Override
    public void writeUniqueIDs(Collection<? extends Map.Entry<String, Integer>> map)
        throws Exception
    {
        next.writeUniqueIDs(map);
    }

    @Override
    public Collection<? extends Map.Entry<String, Integer>> readUniqueIDs()
        throws Exception
    {
        return next.readUniqueIDs();
    }

    @Override
    public int nextUniqueID(String key)
        throws Exception
    {
        return next.nextUniqueID(key);
    }

    @Override
    public Collection<? extends Key> iterKeys(Class type)
        throws Exception
    {
        return next.iterKeys(type);
    }

    @Override
    public void flush()
        throws Exception
    {
        next.flush();
    }

    @Override
    public void dispose()
        throws Exception
    {
        next.dispose();
    }
    
}
